module FluentConditions
  class AccessorDefinerFactory
    def self.make_for(condition_builder, options)
      if options.include?(:values)
        TextValueAccessorDefiner.new(condition_builder, options)
      elsif options.include?(:if)
        CustomAccessorDefiner.new(condition_builder, options)
      else
        BooleanAccessorDefiner.new(condition_builder, options)
      end
    end
  end

  class FluentAccessorMethod
    def initialize(name)
      @name = name
    end

    def positive_check
      @name
    end

    def negative_check
      "not_#{@name}"
    end

    def positive_check_with_result
      "#{@name}?"
    end

    def negative_check_with_result
      "not_#{@name}?"
    end
  end

  class TextValueAccessorDefiner
    def initialize(condition_builder, options)
      @options = options
      @condition_builder = condition_builder
    end

    def define(field)
      @options[:values].each do |value|
        raise AlreadyDefinedError.new(value) if @condition_builder.method_defined?(value)

        accessor_method = FluentAccessorMethod.new(value)
        @condition_builder.class_eval do
          define_method(accessor_method.positive_check) do
            update_and_continue(instance_variable_get(:@object).send(field) == value)
          end

          define_method(accessor_method.positive_check_with_result) do
            update_and_finish(instance_variable_get(:@object).send(field) == value)
          end

          define_method(accessor_method.negative_check) do
            update_and_continue(instance_variable_get(:@object).send(field) != value)
          end

          define_method(accessor_method.negative_check_with_result) do
            update_and_finish(instance_variable_get(:@object).send(field) != value)
          end
        end
      end
    end
  end

  class CustomAccessorDefiner
    def initialize(condition_builder, options)
      @options = options
      @condition_builder = condition_builder
    end

    def define(field)
      field_name = @options[:as]
      condition_check = @options[:if]

      raise AlreadyDefinedError.new(field_name) if @condition_builder.method_defined?(field_name)

      accessor_method = FluentAccessorMethod.new(field_name)
      @condition_builder.class_eval do
        define_method(accessor_method.positive_check) do
          update_and_continue(condition_check.call(instance_variable_get(:@object).send(field)))
        end

        define_method(accessor_method.positive_check_with_result) do
          update_and_finish(condition_check.call(instance_variable_get(:@object).send(field)))
        end

        define_method(accessor_method.negative_check) do
          update_and_continue(!condition_check.call(instance_variable_get(:@object).send(field)))
        end

        define_method(accessor_method.negative_check_with_result) do
          update_and_finish(!condition_check.call(instance_variable_get(:@object).send(field)))
        end
      end
    end
  end

  class BooleanAccessorDefiner
    def initialize(condition_builder, options)
      @options = options
      @condition_builder = condition_builder
    end

    def define(field)
      raise AlreadyDefinedError.new(field) if @condition_builder.method_defined?(field)

      accessor_method = FluentAccessorMethod.new(field)
      @condition_builder.class_eval do
        define_method(accessor_method.positive_check) do
          update_and_continue(instance_variable_get(:@object).send(field))
        end

        define_method(accessor_method.positive_check_with_result) do
          update_and_finish(instance_variable_get(:@object).send(field))
        end

        define_method(accessor_method.negative_check) do
          update_and_continue(!instance_variable_get(:@object).send(field))
        end

        define_method(accessor_method.negative_check_with_result) do
          update_and_finish(!instance_variable_get(:@object).send(field))
        end
      end
    end
  end

  class AccessorDefinerError < RuntimeError; end
  
  class AlreadyDefinedError < AccessorDefinerError
    def initialize(field)
      @field = field
    end
    def to_s
      "Fluent field :#{@field} already defined"
    end
  end

end
